package utils;

import data.component.Line;
import org.jetbrains.annotations.NotNull;
import org.opencv.core.Mat;

import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.awt.geom.Point2D;
import java.awt.geom.GeneralPath;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;

public class Geometry {

    /**
     * 获取2维或3维空间中两点的距离
     *
     * @param p1 点1
     * @param p2 点2
     * @return 距离
     */
    public static Double CalcDistance(Double @NotNull [] p1, Double @NotNull [] p2) {
        if (p1.length == 2) {
            return new Vector(p1, p2).length();
        } else {
            return new Vector3(p1, p2).length();
        }
    }

    /***
     * 判断两线是否共线
     * @param l1 line1
     * @param l2 line2
     * @param AbsTol 容差 absolute tolerance
     * @return Boolean
     */
    public static Boolean CheckCollinear(Line l1, Line l2, int AbsTol) {
        return CheckPointOnLine(l2.start, l1, AbsTol) && CheckPointOnLine(l2.end, l1, AbsTol);
    }

    public static Boolean CheckPointOnLine(Double[] point, Line line, int AbsTol) {
        var point_x = point[0];
        var point_y = point[1];
        var line_s_x = line.start[0];
        var line_s_y = line.start[1];
        var line_e_x = line.end[0];
        var line_e_y = line.end[1];

        double dis;
        if (line_e_x - line_s_x == 0) {
            // 若直线与y轴平行，则距离为点的x坐标与直线上任意一点的x坐标差值的绝对值
            dis = Math.abs(point_x - line_s_x);
        } else if (line_e_y - line_s_y == 0) {
            // 若直线与x轴平行，则距离为点的y坐标与直线上任意一点的y坐标差值的绝对值
            dis = Math.abs(point_y - line_s_y);
        } else {
            // 斜率
            var k = (line_e_y - line_s_y) / (line_e_x - line_s_x);
            // 截距
            var b = line_s_y - k * line_s_x;
            // 带入公式得到距离dis
            dis = Math.abs(k * point_x - point_y + b) / Math.pow(k * k + 1, 0.5);
        }

        return dis < AbsTol;
    }

    /**
     * 统计图像中颜色不为纯黑(0,0,0)的像素的数量
     *
     * @param img 图像
     * @return 非黑像素数量
     */
    public static double CountNonZeroPixel(Mat img) {
        var num = 0;
        for (int i = 0; i < img.height(); i++) {
            for (int j = 0; j < img.width(); j++) {
                var p = img.get(i, j).clone();
                if (!Arrays.equals(p, new double[]{0, 0, 0}))
                    num++;
            }
        }
        return num;
    }

    /**
     * 判断点是否在轮廓内
     *
     * @param point   点
     * @param polygon 轮廓点集
     * @return 是否在轮廓内
     */

    public static Boolean IsPointInPolygon(Double[] point, ArrayList<Double[]> polygon) {
        var point2d = new Point2D.Double(point[0], point[1]);
        var points = polygon.stream().map(p -> new Point2D.Double(p[0], p[1])).collect(Collectors.toList());
        GeneralPath p = new GeneralPath();
        Point2D.Double first = points.get(0);
        p.moveTo(first.x, first.y);
        int size = polygon.size();
        for (int i = 1; i < size; i++) {
            Point2D.Double pa = points.get(i);
            p.lineTo(pa.x, pa.y);
        }
        p.lineTo(first.x, first.y);
        return p.contains(point2d);
    }

    /**
     * 判断点在轮廓内的重载
     *
     * @param point   点
     * @param polygon 轮廓点集
     * @return 是否在轮廓内
     */
    public static Boolean IsPointInPolygon(ArrayList<Double> point, ArrayList<Double[]> polygon) {
        var aPoint = new Double[]{point.get(0), point.get(1)};
        return Geometry.IsPointInPolygon(aPoint, polygon);
    }

    /**
     * 获取线段上离给定点 最近的一点 （一般为垂足或两端点）
     * @param point 给定的点
     * @param line 给定的线段
     * @return 线段上离给定点最近的点
     */
    public static Double[] GetNearestPointOnTheLine(Double[] point, Line line) {
        // 计算垂足 可能在线段外
        var foot = Geometry.GetFoot(point, line);
        if (Geometry.IsPointOnLine(foot, line)) {
            return foot;
        } else {
            return Geometry.CalcDistance(line.start, point) > Geometry.CalcDistance(line.end, point) ? line.end : line.start;
        }
    }

    /**
     * 计算点到直线的垂足
     * @param point 给定的点
     * @param line 线段
     * @return 点到直线的垂足（可能在线段外）
     */
    public static Double[] GetFoot(Double[] point, Line line) {
        var line_p1 = line.start;
        var line_p2 = line.end;
        var x0 = point[0];
        var y0 = point[1];

        var x1 = line_p1[0];
        var y1 = line_p1[1];

        var x2 = line_p2[0];
        var y2 = line_p2[1];

        var k = -((x1 - x0) * (x2 - x1) + (y1 - y0) * (y2 - y1)) / (Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));

        var xn = k * (x2 - x1) + x1;
        var yn = k * (y2 - y1) + y1;
        return new Double[]{xn, yn};

    }

    /**
     * 判断点在是否在线段上
     * @param point 点
     * @param line 线段
     * @return 是否在线段上
     */
    public static Boolean IsPointOnLine(Double[] point, Line line) {
        var vec1 = new Vector(point, line.start);
        var vec2 = new Vector(point, line.end);
        return Vector.Dot(vec1, vec2) <= 0;
    }

    /**
     * 判断两点在某一精度下，是否重合
     * @param p1 点1
     * @param p2 点2
     * @param precision 保留小数点位数
     * @return 是否重合
     */
    public static Boolean IsSamePoint(Double[] p1, Double[] p2, int precision)
    {
        NumberFormat nf = NumberFormat.getNumberInstance();
        // 保留 precision 位小数
        nf.setMaximumFractionDigits(precision);
        // 如果不需要四舍五入，可以使用RoundingMode.DOWN
        nf.setRoundingMode(RoundingMode.UP);
        return nf.format(p1[0]).equals(nf.format(p2[0])) && nf.format(p1[1]).equals(nf.format(p2[1]));
    }

}
